#include <boost/utility.hpp>

#include <kuai/misc/typedef.hpp>
#include <kuai/mol/mol.hpp>

#ifndef _KUAILIB_CT_H_
#define _KUAILIB_CT_H_

namespace kuai {

	

	/**
	* A Connect Table use a connected buffer to store the connection info of a moelcule.
	*/
	class ConnectTable 
	{
	public:
		typedef Atom AtomType;
		typedef Bond BondType;
		typedef ConnectTable MolType;
		typedef SubMolecule<ConnectTable> SubMolType;
		typedef RingFinder<ConnectTable> RingFinder;

	public:
		ConnectTable()
		{ } 
		explicit ConnectTable(Index v[]) {
			_atoms = reinterpret_cast<Atom*>(v + 2);
			_bonds = reinterpret_cast<Bond*>(_atoms + v[0]);
			_degrees = reinterpret_cast<Index*>(_bonds + 3 * v[1]);
			_offsets = _degrees + v[0];
			_adjs = _offsets + v[0];
		} ;

	public:
		Index countAtoms() const {
			return reinterpret_cast<Index&>(_atoms[-2]);
		}
		Index countBonds() const {
			return reinterpret_cast<Index&>(_atoms[-1]);
		}
		const AtomType& getAtom(Index index) const {
			return _atoms[index];
		}
		AtomType& getAtom(Index index) {
			return _atoms[index];
		}
		const BondType& getBond(Index index) const {
			return _bonds[3 * index];
		}
		BondType& getBond(Index index) {
			return _bonds[3 * index];
		}
		const AtomType& getAtom(const BondType& bond, Index index) const {
			Index bID = indexOf(bond);
			Index result = reinterpret_cast<const Index*>(_bonds)[3*bID+1+index];
			return getAtom(result);
		}
		AtomType& getAtom(const BondType& bond, Index index) {
			Index bID = indexOf(bond);
			Index result = reinterpret_cast<const Index*>(_bonds)[3*bID + 1 + index];
			return getAtom(result);
		}
		Index degree(const AtomType& a) const {
			Index id = indexOf(a);
			return _degrees[id];
		}
		const AtomType& getNeighborAtom(const AtomType& a, Index j) const {
			Index id = indexOf(a);
			id = _offsets[id];
			id = _adjs[id+j*2];
			return getAtom(id);
		}
		AtomType& getNeighborAtom(const AtomType& a, Index j) {
			Index id = indexOf(a);
			id = _offsets[id];
			id = _adjs[id+j*2];
			return getAtom(id);
		}
		const BondType& getNeighborBond(const AtomType& a, Index j) const {
			Index id = indexOf(a);
			id = _offsets[id];
			id = _adjs[id+j*2+1];
			return getBond(id);
		}
		BondType& getNeighborBond(const AtomType& a, Index j) {
			Index id = indexOf(a);
			id = _offsets[id];
			id = _adjs[id+j*2+1];
			return getBond(id);
		}

		const BondType* getBond(const AtomType& a1, const AtomType& a2) const {
			Index d = degree(a1);
			for (Index i = 0; i < d; ++i) {
				if (&getNeighborAtom(a1, i) == &a2) {
					return &getNeighborBond(a1, i);
				}
			}
			return NULL;
		};
		BondType* getBond(const AtomType& a1, const AtomType& a2) {
			Index d = degree(a1);
			for (Index i = 0; i < d; ++i) {
				if (&getNeighborAtom(a1, i) == &a2) {
					return &getNeighborBond(a1, i);
				}
			}
			return NULL;
		};

		Index indexOf(const AtomType& v0) const {
			Index result = &v0 - _atoms;
			if (result < countAtoms()) {
				return result;
			}
			return INVALID_INDEX;
		};

		Index indexOf(const BondType& v0) const {
			Index result = &v0 - _bonds;
			if (result < countBonds() * 3) {
				assert (result %  3 == 0);
				return result / 3;
			}
			return INVALID_INDEX;
		};

		bool isEmpty() const {
			return countAtoms() == 0;
		};

		static size_t buffer_size(Index nAtoms, Index nBonds) {
			return 3 * nAtoms + 7 * nBonds + 2;
		}

		static void makeBuffer(const Index input[], Index buffer[]);

	private:
		AtomType* _atoms;
		BondType* _bonds;
		Index* _degrees;
		Index* _offsets;
		Index* _adjs;
	};

	std::vector<boost::shared_ptr<ConnectTable::SubMolType> > getConnectedParts(ConnectTable& mol);
	std::vector<boost::shared_ptr<ConnectTable::SubMolType> > getConnectedParts(ConnectTable::SubMolType& mol);

	Index setupAromaticBond(ConnectTable& table, const Index bondrank[], const Integer charges[]);
	Index setupAromaticBond(const ConnectTable& table, const Index bondrank[], const Integer charges[], Index result[]);

	void getTotalBondOrder(const ConnectTable& ct, const Integer charges[], Index result[]);
	inline std::vector<Index> getTotalBondOrder(const ConnectTable& ct, const Integer charges[]) {
		std::vector<Index> result(ct.countAtoms(), 0);
		getTotalBondOrder(ct, charges, &result[0]);
		return result;
	}
	void getImplicitHydrogen(const ConnectTable& ct, const Integer charges[], Index result[]);
	inline std::vector<Index> getImplicitHydrogen(const ConnectTable& ct, const Integer charges[]) {
		std::vector<Index> result(ct.countAtoms(), 0);
		getImplicitHydrogen(ct, charges, &result[0]);
		return result;
	};
}

#endif
